package com.nts.pmchelperdemo3.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nts.pmchelperdemo3.constant.MessageConstant;
import com.nts.pmchelperdemo3.dto.MachineLatestScheduleDTO;
import com.nts.pmchelperdemo3.dto.SchedulingDTO;
import com.nts.pmchelperdemo3.dto.SchedulingDetailDTO;
import com.nts.pmchelperdemo3.entity.Machine;
import com.nts.pmchelperdemo3.entity.Order;
import com.nts.pmchelperdemo3.entity.Scheduling;
import com.nts.pmchelperdemo3.entity.Tool;
import com.nts.pmchelperdemo3.exception.*;
import com.nts.pmchelperdemo3.mapper.MachineMapper;
import com.nts.pmchelperdemo3.mapper.OrderMapper;
import com.nts.pmchelperdemo3.mapper.SchedulingMapper;
import com.nts.pmchelperdemo3.mapper.ToolMapper;
import com.nts.pmchelperdemo3.service.MachineService;
import com.nts.pmchelperdemo3.service.OrderService;
import com.nts.pmchelperdemo3.service.SchedulingService;
import com.nts.pmchelperdemo3.util.DateUtil;
import com.nts.pmchelperdemo3.util.OrderSortUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SchedulingServiceImpl extends ServiceImpl<SchedulingMapper,Scheduling> implements SchedulingService {
    @Autowired
    private SchedulingMapper schedulingMapper;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private ToolMapper toolMapper;

    @Autowired
    private MachineMapper machineMapper;

    @Autowired
    private MachineService machineService;

    @Autowired
    private OrderSortUtil orderSortUtil;


    @Value("${scheduling.work-time.start}")
    private String workTimeStart;

    @Value("${scheduling.work-time.end}")
    private String workTimeEnd;

    @Value("${scheduling.debug-time}")
    private Integer debugTime = 2;

    @Override
    @Transactional
    public List<SchedulingDTO> generateScheduling(Long orderId, Integer taskType) {
        //检验订单类型
        //Long mainOrderId = mergeOrdersIfPossible(orderId);
       /* if (!mainOrderId.equals(orderId)){
            orderId = mainOrderId;
        }*/
        Order order = orderMapper.selectById(orderId);
        if (order == null) {
            throw new OrderNotFoundException(MessageConstant.ORDER_NOT_FOUND);
        }
        if (order.getPriority() > 1){
            removeLowerPrioritySchedulings(order.getPriority());
        }
        //检验任务类型
        if ((taskType == 0 && order.getStatus() != 0) || (taskType == 1 && order.getStatus() != 2)) {
            throw new TaskTypeMismatchException(MessageConstant.TASK_TYPE_NOT_MATCH);
        }
        //从数据库或新订单中获取刀具的处理时间
        double processTime;
        Tool tool = toolMapper.selectOne(new QueryWrapper<Tool>().eq("tool_name", order.getToolName()));
        if (taskType == 0)//磨刀
        {
            processTime = (tool != null && tool.getType() == 0) ? tool.getSharpenTime() : order.getCustomSharpenTime();
        } else {//涂层
            processTime = (tool != null && tool.getType() == 0) ? tool.getCoatTime() : order.getCustomCoatTime();
        }

        //获取目前可用机器
        List<Machine> machines = machineService.getAvailableMachinesByType(taskType);
        for (Machine machine : machines){
            machine.setCurrentScheduleEndTime(machineService.regetTaskEndTime(machine.getId()));
        }
        if (machines.isEmpty()) {
            throw new MachineFullException(MessageConstant.MACHINE_IS_FULL + taskType);
        }

        //分配任务
        int totalQuantity = order.getToolQuantity();
        int machineType = taskType;
        List<Scheduling> schedulings = new ArrayList<>();

        //最早空闲算法
        machines.sort((m1, m2) -> {
            LocalDateTime d1 = m1.getCurrentScheduleEndTime() != null ? m1.getCurrentScheduleEndTime() : LocalDateTime.now();
            LocalDateTime d2 = m2.getCurrentScheduleEndTime() != null ? m2.getCurrentScheduleEndTime() : LocalDateTime.now();
            return d1.compareTo(d2);
        });

        int remaining = totalQuantity;
        for (Machine machine : machines) {
            if (remaining <= 0)
                break;
            //计算单台机器可处理数量
            int handleQty = Math.min(remaining, machine.getMaxCapacity());
            remaining -= handleQty;
            //计算任务开始时间
            LocalDateTime startTime = getTaskStartTime(machine, taskType);
            //计算任务结束时间
            LocalDateTime endTime = DateUtil.calculateWorkEndTime(startTime, processTime, workTimeStart,workTimeEnd);

            //创建排班记录
            Scheduling scheduling = new Scheduling();
            scheduling.setMachineId(machine.getId());
            scheduling.setOrderId(orderId);
            scheduling.setTaskType(taskType);
            scheduling.setHandleQuantity(handleQty);
            scheduling.setTaskStartTime(startTime);
            scheduling.setTaskEndTime(endTime);
            schedulings.add(scheduling);

            //更新机器截止日期
            machineService.updateMachineDeadline(machine.getId(),endTime.toString());
        }

        if (remaining > 0){
            throw new MachineNotEnoughException(MessageConstant.MACHINE_NOT_ENOUGH);
        }

        //保存排班记录
        schedulings.forEach(schedulingMapper::insert);
        if (taskType == 0){
            orderSortUtil.updateOrderStatus(orderId,1);
        }else {
            orderSortUtil.updateOrderStatus(orderId,3);
        }
        return schedulings.stream().map(this::convertToDTO).collect(Collectors.toList());
    }

    @Override
    public List<SchedulingDTO>getSchedulingByOrder(Long orderId){
        QueryWrapper<Scheduling>queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_id",orderId);
        List<Scheduling> schedulings = schedulingMapper.selectList(queryWrapper);
        return schedulings.stream().map(this::convertToDTO).collect(Collectors.toList());
    }

    @Override
    public List<SchedulingDTO>getSchedulingByMachine(Long machineId,String date){
        QueryWrapper<Scheduling>queryWrapper  = new QueryWrapper<>();
        queryWrapper.eq("machine_id",machineId);
        if (date != null && !date.isEmpty()){
            queryWrapper.between("task_start_time",date + "00:00:00",date + "23:59:59");
        }
        queryWrapper.orderByAsc("task_start_time");
        List<Scheduling> schedulings = schedulingMapper.selectList(queryWrapper);
        return schedulings.stream().map(this::convertToDTO).collect(Collectors.toList());
    }

    @Override
    @Transactional
    public SchedulingDTO adjustScheduling(Long schedulingId,SchedulingDTO schedulingDTO){
        Scheduling scheduling = schedulingMapper.selectById(schedulingId);
        if (scheduling == null){
            throw new ScheduleNullException(MessageConstant.SCHEDULE_IS_NULL);
        }

        //更新时间与数量
        scheduling.setTaskStartTime(schedulingDTO.getTaskStartTime());
        scheduling.setTaskEndTime(schedulingDTO.getTaskEndTime());
        scheduling.setHandleQuantity(schedulingDTO.getHandleQuantity());
        schedulingMapper.updateById(scheduling);

        //更新机器的截止日期
        machineService.updateMachineDeadline(scheduling.getMachineId(),scheduling.getTaskEndTime().toString());
        return convertToDTO(scheduling);
    }

    @Override
    @Transactional
    public void syncOrderStatusByTime(){
        //查询所有正在工作的订单
        QueryWrapper<Order>queryWrapper = new QueryWrapper<>();
        queryWrapper.lt("status",4);
        List<Order>orders = orderMapper.selectList(queryWrapper);

        LocalDateTime now = LocalDateTime.now();
        for (Order order : orders){

            if (order.getStatus() == 0){
                try {
                    // 调用排班生成方法，任务类型为0（磨刀）
                    this.generateScheduling(order.getId(), 0);
                    //orderSortUtil.updateOrderStatus(order.getId(),1);
                } catch (Exception e) {
                    // 处理异常（如机器不足等），可根据业务需求决定是否回滚订单
                    throw new RuntimeException("订单创建成功，但磨刀排班生成失败：" + e.getMessage());
                }
            }
            if (order.getStatus() == 1) {
                //磨刀
                List<SchedulingDTO>sharpenSchedulings = getSchedulingByOrder(order.getId()).stream().filter(dto -> dto.getTaskType() == 0).collect(Collectors.toList());
                boolean allSharpenDone = sharpenSchedulings.stream().allMatch(dto -> dto.getTaskEndTime().isBefore(now));
                if (allSharpenDone){
                    orderSortUtil.updateOrderStatus(order.getId(),2);
                }
            }

            //检查涂层
            if (order.getStatus() == 3){
                //涂层
                List<SchedulingDTO>coatSchedulings = getSchedulingByOrder(order.getId()).stream().filter(dto-> dto.getTaskType() == 1).collect(Collectors.toList());
                boolean allCoatDone = coatSchedulings.stream().allMatch(dto->dto.getTaskEndTime().isBefore(now));
                if (allCoatDone){
                    orderSortUtil.updateOrderStatus(order.getId(),4);
                }
            }
        }
    }

    @Override
    @Transactional
    public Long mergeOrdersIfPossible(Long newOrderId){
        //查询新订单信息
        Order newOrder = orderMapper.selectById(newOrderId);
        if (newOrder == null){
            throw new OrderNotFoundException("新" + MessageConstant.ORDER_NOT_FOUND);

        }
        if (newOrder.getStatus() != 0){
            //只合并还没开始工作的订单
            return newOrderId;
        }
        //查询符合条件的订单
        QueryWrapper<Order>queryWrapper = new QueryWrapper<>();
        //条件：相同刀具，交付期小于三天
        queryWrapper.eq("tool_name",newOrder.getToolName()).eq("status",0).ne("id",newOrderId).between("end_time",newOrder.getEndTime().minusDays(3),newOrder.getEndTime().plusDays(3));

        List<Order>mergeableOrders = orderMapper.selectList(queryWrapper);
        if (mergeableOrders.isEmpty()){
            return newOrderId;//无法合并
        }

        //确定主订单（优先级最高
        Order mainOrder = newOrder;
        for (Order order : mergeableOrders){
            if (order.getPriority() > mainOrder.getPriority() || (order.getPriority().equals(mainOrder.getPriority()) && order.getStartTime().isBefore(mainOrder.getStartTime())))
            {
                mainOrder = order;
            }
        }

        //合并订单
        int totalQuantity = newOrder.getToolQuantity();
        for (Order order : mergeableOrders){
            totalQuantity += order.getToolQuantity();
            order.setStatus(5);
            orderMapper.updateById(order);
        }

        //更新主订单
        mainOrder.setToolQuantity(totalQuantity);
        mainOrder.setPriority(Math.max(mainOrder.getPriority(), mergeableOrders.stream().map(Order::getPriority).max(Integer::compare).orElse(mainOrder.getPriority())));
        mainOrder.setStartTime(mergeableOrders
                .stream()
                .map(Order::getStartTime)
                .min(LocalDateTime::compareTo)
                .orElse(mainOrder.getStartTime()));
        mainOrder.setEndTime(mergeableOrders
                .stream()
                .map(Order::getEndTime)
                .max(LocalDateTime::compareTo)
                .orElse(mainOrder.getEndTime()));
        orderMapper.updateById(mainOrder);
        return mainOrder.getId();
    }

    @Override
    public List<MachineLatestScheduleDTO> getAllSchedulingsWithEndTime(){
        List<Scheduling> allSchedulings = schedulingMapper.selectList(null);

        Map<Long,Map<String ,Object>> machineStats = allSchedulings.stream()
                .collect(Collectors.groupingBy(Scheduling::getMachineId,Collectors.collectingAndThen(Collectors.toList(),
                        list ->{
                    Map<String ,Object> stats = new HashMap<>();
                    stats.put("LastEndTime",list.stream()
                            .map(Scheduling::getTaskEndTime)
                            .max(LocalDateTime::compareTo)
                            .orElse(null));
                    stats.put("count",list.size());
                    return stats;
                        })));
        return machineMapper.selectList(null).stream()
                .map(machine -> {
                    MachineLatestScheduleDTO dto = new MachineLatestScheduleDTO();
                    dto.setMachineId(machine.getId());
                    dto.setMachineName(machine.getMachineName());
                    dto.setMachineType(machine.getMachineType() == 0 ? "磨刀机" : "涂层机");
                    Map<String,Object> stats = machineStats.getOrDefault(machine.getId(),new HashMap<>());
                    dto.setLatestEndTime((LocalDateTime) stats.get("latestEndTime"));
                    dto.setSchedulingCount((Integer) stats.getOrDefault("count",0));
                    return dto;
                }).collect(Collectors.toList());
    }

    @Override
    public IPage<SchedulingDetailDTO> getSchedulingDetail(Page<SchedulingDetailDTO> page){
        Page<Scheduling> schedulingPage = new Page<>(page.getCurrent(), page.getSize());

        IPage<Scheduling> schedulingIPage = baseMapper.selectPage(schedulingPage,new QueryWrapper<Scheduling>().orderByAsc("task_start_time"));

        IPage<SchedulingDetailDTO> dtoiPage = schedulingIPage.convert(scheduling -> {
            SchedulingDetailDTO dto = new SchedulingDetailDTO();
            BeanUtils.copyProperties(scheduling,dto);
            LocalDateTime now = LocalDateTime.now();
            if (scheduling.getTaskEndTime().isBefore(now)){
                dto.setStatus(2);

            } else if (scheduling.getTaskStartTime().isBefore(now)) {
                dto.setStatus(1);
            }else {
                dto.setStatus(0);
            }
            return dto;
        });
        return dtoiPage;
    }

    @Override
    public List<Scheduling> removeLowerPrioritySchedulings(Integer newOrderPriority) {
        LocalDateTime now = LocalDateTime.now();
        QueryWrapper<Scheduling> queryWrapper = new QueryWrapper<>();
        queryWrapper.ge("task_start_time",now);

        List<Scheduling>pendingSchedulings = schedulingMapper.selectList(queryWrapper);
        if (pendingSchedulings.isEmpty()){
            return new ArrayList<>();
        }

        List<Scheduling> toRemove = new ArrayList<>();
        for (Scheduling scheduling : pendingSchedulings){
            Order order = orderMapper.selectById(scheduling.getOrderId());
            if (order != null && order.getPriority() < newOrderPriority){
                toRemove.add(scheduling);
            }
        }

        if (!toRemove.isEmpty()){
            List<Long> ids = toRemove.stream()
                    .map(Scheduling::getId)
                    .collect(Collectors.toList());
            schedulingMapper.deleteBatchIds(ids);

            for (Scheduling s : toRemove){
                Order order = orderMapper.selectById(s.getOrderId());

                if (order != null){
                    if (s.getTaskType() == 0){
                        order.setStatus(0);
                    }else {
                        order.setStatus(2);
                    }
                    orderMapper.updateById(order);
                }

                machineService.resetMachineDeadlineIfNeeded(s.getMachineId(),s.getTaskEndTime());
            }
        }
        return toRemove;
    }

    private SchedulingDTO convertToDTO(Scheduling scheduling){
        SchedulingDTO dto = new SchedulingDTO();
        BeanUtils.copyProperties(scheduling,dto);
        dto.setTaskTypeName(scheduling.getTaskType() == 0 ? "磨刀" : "涂层");
        return dto;
    }

    private LocalDateTime getTaskStartTime(Machine machine,Integer taskType){
        LocalDateTime machineFreeTime = machine.getCurrentScheduleEndTime() != null ? machine.getCurrentScheduleEndTime() : LocalDateTime.now();
        if (taskType == 0){
            return DateUtil.addWorkHours(machineFreeTime,debugTime,workTimeStart,workTimeEnd);
        }else {
            return machineFreeTime;
        }
    }

}
